Tutorials > " . "XUL Tutorial"; $pagetitle="8.3 - Manipulating RDF Datasources"; $customsidebar = "xultu-sidebar.php"; include "header.php"; ?>

Manipulating RDF Datasources

This section explains how to manipulate RDF with a script.

RDF Datasources with XPCOM

Templates can be used to extract data from an RDF datasource and build content based on that data. However, the datasources can be examined from a script. You can get the datasource from an element built from a template, and pick out individual resources from it. This also allows you to modify the datasource.

The XPCOM interface to RDF involves a number of interfaces. The following lists some of the interfaces invloved:

nsIRDFService A global RDF service. It is used to generate resource objects that can uniquely identify a resource within an RDF data source.
nsIRDFDataSource An RDF datasource, either a built-in one or one from an RDF file. Methods allow you to get and set values.
nsIRDFContainer A container node within an RDF data source. Methods allow you to add and remove resources.
nsIRDFContainerUtils This interface has some handy container methods to create Seq, Bag and Alt resources.

In the find files dialog, we could implement the ability to store the most recently searched for items. The search textbox could be replaced by an editable drop-down that contains a list of items that were recently searched for. We will add this capability now.

This will really only work if the dialog has access to a location on disk where the recent search items list can be stored. The most likely places for this are the user's profile directory or a directory the user chooses themselves. Although we won't do that here, the user's profile directory can be retrieved using the '@mozilla.org/file/directory_service' component. To simplify the example, we'll just put a file path directly in the XUL in a datasources attribute.

We could store the recent searches list in a plain text file. However, we can use RDF which already has the ability to read and write its data and update a widget generated from a template automatically. First, the changes to the XUL file. We'll replace the textbox with an editable drop-down list. Replace the value of the datasources attribute with a suitable path. (The file should exist already).

<menulist id="find-text" flex="1" style="min-width: 15em;"
          editable="true"
          datasources="file:///mozilla/recents.rdf"
          ref="http://www.xulplanet.com/rdf/recent/all">
  <template>
    <menupopup>
      <menuitem label="rdf:http://www.xulplanet.com/rdf/recent#Label" uri="rdf:*"/>
    </menupopup>
  </template>
</menulist>

All XUL elements that have their children generated by a template have a database property that refers to a nsIRDFDataSource object. This object can then be used to read from and modify the data source used. The database property is placed on the element that has the datasources attribute. This will typically be a tree or, as is the case here, a menulist element.

The database property contains a list (actually an nsISimpleEnumerator) of each of the datasources that were specified in the datasources attribute. That means that we need to iterate over each element, even if there is only one. The following example shows how to do this, assuming only one datasource exists:

var dsource;
var menulist=document.getElementById("find-text");
var sources=menulist.database.GetDataSources();

if (sources.hasMoreElements()){
  dsource=sources.getNext();
}
dsource=dsource.QueryInterface(Components.interfaces.nsIRDFDataSource);

First, we get a reference to a menulist, which here has an id of find-text. Next we get the list of datasources from the menulist. The nsISimpleEnumerator interface has two methods (it is similar to Java's Enumeration interface). We loop through the elements in the enumeration and, because we assume there is only one, we'll just get it with the getNext method. Finally, we'll call QueryInterface to ensure that it is an nsIRDFDataSource.

We'll use code similar to this to create the recent searches list. First, however, let's initialize the components that we want to use. We'll need three components. The interface nsIRDFService will be used to create resource objects. The interface nsIRDFContainer will be used to add resources to the data source. The third interface, nsIRDFContainerUtils will be used only when the recent searches list is first used, to create the root node. In a script file (findfile.js), add the following code to the top of it. This will be executed when the find files dialog is loaded.

const RDFC = '@mozilla.org/rdf/container;1';
RDFC = Components.classes[RDFC].createInstance(Components.interfaces.nsIRDFContainer);

const RDFCUtils = '@mozilla.org/rdf/container-utils;1';
RDFCUtils = Components.classes[RDFCUtils].getService(Components.interfaces.nsIRDFContainerUtils);

const RDF = '@mozilla.org/rdf/rdf-service;1';
RDF = Components.classes[RDF].getService(Components.interfaces.nsIRDFService);

This code will create the three services that we need to use. The syntax is similar to other XPCOM object creation code. The first three lines get a reference to an nsIRDFContainer object. Next, we perform a similar operation to get the nsIRDFContainerUtils object. Finally, we repeat again for the nsIRDFService.

Next, we create an initialize function, which we'll call in the onload handler of the window. It will be executed when the window is displayed. Within this code, we'll add code to initialize the RDF objects we created above.

findfile.xul:

<window onload="initSearchList()" ... >

findfile.js:

var dsource;

function initSearchList()
{
  var recentlist=document.getElementById("find-text");
  var sources=recentlist.database.GetDataSources();
  var rootnode=RDF.GetResource("http://www.xulplanet.com/rdf/recent/all");
  
  while (sources.hasMoreElements()){
    try {
      dsource=sources.getNext();
      dsource=dsource.QueryInterface(Components.interfaces.nsIRDFDataSource);
  
      RDFC.Init(dsource,rootnode);
    } catch (e) {
      RDFCUtils.MakeSeq(dsource,rootnode);
      RDFC.Init(dsource,rootnode);
    }
  }
}

Let's break down the initSearchList function:

The interface nsIRDFService contains a method GetResource that creates a resource object for us, from the string passed in as an argument. This method does not get the value of anything, it simply converts a string into a resource object that can be used to get the value from the datasource. The RDF interfaces do not use strings but instead use resources to refer to things. The value returned by GetResource is of the type nsIRDFResource.

Now that the objects have been initialized, we can add and remove information from the datasource. There are two methods needed depending on whether you want to add a resource to a container or add an assertion from one resource to another. These two cases correspond to adding a bookmark and adding a property such as the title or URL to a bookmark.

We'll add a new entry to the searched for items list when the user clicks the Find button. We'll oversimplify it a bit in several ways. For one, we won't bother checking for duplicate entries. Second, we won't concern ourselves about limiting the length of the list.

Let's add another function that is called from within the doFind function.

function doFind()
{
  var recentlist=document.getElementById("find-text");
  var fldval=recentlist.value;

  addSearchedItem(fldval);

.
.
.

This code gets the value entered into the menulist's textbox. We pass the text to the function addSearchedItem which will be defined next.

function addSearchedItem(txt)
{
  var newnode=RDF.GetResource("http://www.xulplanet.com/rdf/recent/all/item"+(RDFC.GetCount()+1));
  var labelprop=RDF.GetResource("http://www.xulplanet.com/rdf/recent#Label");
  var newvalue=RDF.GetLiteral(txt);
  
  dsource.Assert(newnode,labelprop,newvalue,true);
  RDFC.InsertElementAt(newnode,1,true);
  
  dsource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush();
} 

This code does three things, it adds a new resource, it adds a new assertion which holds the value, and then it writes out the modified datasource. Let's break down the code:

Not all datasources can be modified. All datasources loaded from file and resource URLs can be written to as well as some of the internal datasources.

If you open the find files dialog now and enter some text, and press Find, you will find that the text appears as one of the choices in the drop-down. Even if you exit and reload, the text will remain in the drop-down.

In order to check for duplicate entries, we could check the existing resources, by using the functions hasAssertion or GetAllResources of the interface nsIRDFDataSource.


(Next) Next, we'll see how to access the system clipboard for copy and paste operations.

Find files example so far: Source View

Note that you will have to load the file from a chrome URL to see the recent files list in use.