Sharing Data Between Data Sets

It is sometimes necessary to use 2 or more data sets that derive their data from the same XML or JSON document. This typically isn't a problem because the XML Data Set uses an internal load manager to cache requests for the same document, but there are times when the cache needs to be turned off because the data loaded with the same URL can yield different results. The following examples use XML as the data format, but the same technique is applicable when using JSON data.

Here's an example of 2 data sets that load the same XML data with this caching behavior turned off:

var dsProducts   = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });
var dsCategories = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product/category", { useCache: false, distinctOnLoad: true, sortOnLoad: "category" });

Now the problem with this scenario, is that the 2 data sets will now cause 2 hits to the same server for the same document. Not very efficient. You can however make use of data set observers to make this a bit more efficient:

// Create the dsProducts data set.

var dsProducts = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });

// Create the dsCategories data set, but pass a null for the URL parameter so that it doesn't load
// any data.

var dsCategories = new Spry.Data.XMLDataSet(null, "/products/product/category", { useCache: false, distinctOnLoad: true, sortOnLoad: "category" });

// Define a function that we will register as an observer on dsProducts.

function updateCategories(notificationType, notifier, data)
{
  // We are only interested in "onPostLoad" notifications.
  // If it is any other notification, just bail.

  if (notificationType != "onPostLoad")
    return;

  // We recieved an "onPostLoad" notification from dsProducts!
  // Ask it for its XML document. If it has one, tell dsCategories
  // to extract its data from that document.

  var doc = dsProducts.getDocument();
  if (doc)
    dsCategories.setDataFromDoc(doc);
};

// Register our function as an observer on dsProducts.

dsProducts.addObserver(updateCategories);

In the example above we are chaining dsCategories to dsProducts. We did this by setting the URL argument for dsCategories' data set constructor to null so that it doesn't attempt to load anything if loadData() called on it. The 2nd thing we did was define a function that would observe notifications on dsProducts. The idea here is that any time dsProducts loads data off the network, we want to catch the "onPostLoad" notification it fires off so we can grab its document and use that to extract the data for dsCategories.

The code above looks pretty huge with all of the comments, so here it is again with all of the comments stripped out.

var dsProducts = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });
var dsCategories = new Spry.Data.XMLDataSet(null, "/products/product/category", { useCache: false, distinctOnLoad: true, sortOnLoad: "category" });

function updateCategories(notificationType, notifier, data)
{
  if (notificationType != "onPostLoad")
    return;
  var doc = dsProducts.getDocument();
  if (doc)
    dsCategories.setDataFromDoc(doc);
};

dsProducts.addObserver(updateCategories);

Now, lets look at a slightly more complicated scenario, where we have one data set that relies on data from another data set for its XPath:

var dsProducts   = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });
var dsFeatures = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product[name = '{ds1::name}']/features/feature", { useCache: false });

This scenario is almost identical to the first example, except that the dsFeatures XPath relies on the value of the "name" column in the current row of dsProducts. The solution to this is identical to the first example, except that we need to listen for one extra notification, specifically "onCurrentRowChanged":

// Create the dsProducts data set.

var dsProducts = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });

// Create the dsFeatures data set, but pass a null for the URL parameter so that it doesn't load
// any data.

var dsFeatures = new Spry.Data.XMLDataSet(null, "/products/product[name = '{dsProducts::name}']/features/feature", { useCache: false });

// Define a function that we will register as an observer on dsProducts.

function updateFeatures(notificationType, notifier, data)
{
  // We are only interested in "onPostLoad" notifications.
  // If it is any other notification, just bail.

  if (notificationType != "onPostLoad" && notificationType != "onCurrentRowChanged")
    return;

  // We recieved an "onPostLoad" or "onCurrentRowChanged notification
  // from dsProducts! Ask it for its XML document. If it has one, tell
  // dsFeatures to extract its data from that document.

  var doc = dsProducts.getDocument();
  if (doc)
    dsFeatures.setDataFromDoc(doc);
};

// Register our function as an observer on dsProducts.

dsProducts.addObserver(updateFeatures);

Here is the same code without all of the comments:

var dsProducts = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });
var dsFeatures = new Spry.Data.XMLDataSet(null, "/products/product[name = '{dsProducts::name}']/features/feature", { useCache: false });

function updateFeatures(notificationType, notifier, data)
{
  if (notificationType != "onPostLoad" && notificationType != "onCurrentRowChanged")
    return;
  var doc = dsProducts.getDocument();
  if (doc)
    dsFeatures.setDataFromDoc(doc);
};

dsProducts.addObserver(updateFeatures);

Live Example

Below is a live example of what was mentioned above. If you load this sample page in FireFox and you have the FireBug add-on installed, you will see that the XML file is only loaded once for all 3 data sets. This live version is using a merged version of the two observer functions above, which looks like this:

var dsProducts = new Spry.Data.XMLDataSet("../../demos/products/products.xml", "/products/product", { useCache: false });
var dsCategories = new Spry.Data.XMLDataSet(null, "/products/product/category", { useCache: false, distinctOnLoad: true, sortOnLoad: "category" });
var dsFeatures = new Spry.Data.XMLDataSet(null, "/products/product[name = '{dsProducts::name}']/features/feature", { useCache: false });

function updateCategoriesAndFeatures(notificationType, notifier, data)
{
	if (notificationType != "onCurrentRowChanged" && notificationType != "onPostLoad")
		return;

	var doc = dsProducts.getDocument();
	if (doc)
	{
		if (notificationType == "onPostLoad")
			dsCategories.setDataFromDoc(doc);
		dsFeatures.setDataFromDoc(doc);
	}
};

dsProducts.addObserver(updateCategoriesAndFeatures);

Click on any of the items in the dsProducts column, and the dsFeatures column will update. Because we've chained the data sets together with observers, so that they share the same document, the server is only hit once.

dsProducts dsFeatures dsCategories
  • {name}
  • {feature}
  • {category}