This article assumes that you are familar with the basics of XForms and at least one syndication format such as RSS. The article uses RSS 1.0, but the concepts are the same for not only any version of RSS, but also Atom and any other XML-based format. The code was tested using Mozilla Firefox with the XForms extension (see Resources), but the concept should work in any XForms-capable browser.
What you're trying to accomplish
The idea is to create a page that enables the user to request a specific feed to read based on its URL, displaying its information on the page, as shown in Figure 1.
Figure 1. The reader
The page also includes a button that lets the reader switch to an editor, as shown in Figure 2.
Figure 2. The editor
The editor enables the user to change existing information, add a new item, delete existing items, and save the feed, assuming that you have the appropriate permissions.
The first step is to create the basic page that enables the user to specify a feed to read. To do that, start with a basic XForms form, embedded in an XHTML page (see Listing 1).
Listing 1.The most basic form
<?xml version="1.0" encoding="ASCII"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<head>
<title>RSS Reader</title>
<xforms:model>
<xforms:instance id="content" src="blankfeed.xml" />
</xforms:model>
<link href="gen_default.css" rel="stylesheet"/>
</head>
<body>
<h1>The XForms Feed Reader</h1>
</body>
</html>
|
The form doesn't specify any visible controls, but it does create the model and the instance, which will ultimately define the data the user sees. The blankfeed.xml file is a placeholder that contains only enough structure to make sure the controls you ultimately build can bind properly, as shown in Listing 2.
Listing 2. The blankfeed.xml file
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns="http://purl.org/rss/1.0/">
<channel rdf:about="">
<title></title>
<link></link>
<description></description>
<dc:language>en-us</dc:language>
<dc:creator />
<dc:date></dc:date>
<items>
<rdf:Seq>
</rdf:Seq>
</items>
</channel>
</rdf:RDF>
|
Now you need a way for the user to specify the feed with which he or she wants to work. Fortunately, you can take advantage of how XForms works to accomplish this task. You're probably already aware of how changing a value in one XForms control can lead to the browser automatically displaying new information somewhere else. What you might not know is that the XForms processor reacts that way to virtually any change. For example, you can create a form that automatically changes the value of the instance element's src attribute (see Listing 3).
Listing 3. Requesting a new feed
...
<h1>The XForms Feed Reader</h1>
<p>Enter the URL of a feed to read:
<form name="urlform" id="urlform"
onsubmit="document.getElementById('content').setAttribute('src',>
document.forms['urlform'].elements['targeturl'].value);
return false;">
<input type="text" name="targeturl" id="targeturl" />
<input type="submit" value="go" />
</form>
</p>
</body>
</html>
|
The form doesn't actually take the browser to a new page. Instead, it acts as a way to execute a bit of JavaScript code. The user enters a URL in the targeturl field, and when he or she submits the form, either by clicking the Enter key or by clicking the Submit button, the browser executes the onsubmit handler. That script gets a reference to the src attribute of the instance by referencing its id attribute, content. From there, it sets that value to match the text entered in the targeturl field.
Once that value changes, the instance gets reloaded. You can load the page and submit the form, but in order to see any changes, you'll have to add some visible controls.
Creating the reader part of the page is very straightforward. You are dealing with a well-known format, in this case RSS 1.0 (see Listing 4).
Listing 4. The target file
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/rss/1.0/">
<channel rdf:about="http://www.chaosmagnet.com/blog/">
<title>Chaos Magnet</title>
<link>http://www.chaosmagnet.com/blog/</link>
<description>The personal and professional ramblings of technology
author Nicholas Chase.</description>
<dc:language>en-us</dc:language>
<dc:creator />
<dc:date>2006-12-22T09:53:16-05:00</dc:date>
<items>
<rdf:Seq>
<rdf:li rdf:resource="http://www.chaosmagnet.com/blog/archives/000840.html" />
<rdf:li rdf:resource="http://www.chaosmagnet.com/blog/archives/000839.html" />
...
</rdf:Seq>
</items>
</channel>
<item rdf:about="http://www.chaosmagnet.com/blog/archives/000840.html">
<title>A quick hit on Rick Berman</title>
<link>http://www.chaosmagnet.com/blog/archives/test.html</link>
<description>I believe in reading source material, but I just can't bring myself to
do it in this case. Not for just one quick comment. SyFy Portal has a discussion
of an interview
with Rick Berman, from a transcript of a...</description>
<dc:subject>Rants</dc:subject>
<dc:creator>roadnick</dc:creator>
<dc:date>2006-12-22T09:53:16-05:00</dc:date>
</item>
<item rdf:about="http://www.chaosmagnet.com/blog/archives/000839.html">
<title>Now everyone can add "and 2006 Time's Person Of The Year" to their bio</title>
<link>http://www.chaosmagnet.com/blog/archives/000839.html</link>
<description>It may sound a little silly, but that's what Time did for
2006, in choosing it's
Person of the Year. The premise is that ...</description>
<dc:subject>Fun stuff</dc:subject>
<dc:creator>roadnick</dc:creator>
<dc:date>2006-12-21T09:00:21-05:00</dc:date>
</item>
...
</rdf:RDF>
|
The file includes a variety of namespaces, including RSS, the Dublin Core, and RDF. You'll need to take that into account when creating the XForms form. The reader form itself is very straightfoward (see Listing 5).
Listing 5. The reader form
...
<input type="submit" value="go" />
</form>
</p>
<h1 align="center"><xforms:output
ref="/rdf:RDF/rss:channel/rss:title" /></h1>
<xforms:repeat nodeset="/rdf:RDF/rss:item">
<h2><xforms:output ref="rss:title"/></h2>
<xforms:output ref="rss:description"/>
<xforms:output ref="rss:link"/>
<xforms:output ref="dc:date" />
<i><xforms:output ref="dc:subject" /></i>
</xforms:repeat>
</body>
</html>
|
First, you display a title on the page by working your way down into the title included as part of the channel element. Notice the use of namespace prefixes. Even though the elements in the RSS namespace didn't have prefixes in the original file, they still belong to that namespace. Because this file uses XHTML as the default namespace, you'll need to use the rss: prefix to specify the RSS elements.
You'll use the repeat element to loop through each of the rss:item elements. Finally, use the output element to display individual items and their information.
The result, styled using CSS, looks like Figure 3.
Figure 3. The reader
Okay, that was simple enough. You can, of course, do more complex formatting and layout, but this is the basic method.
But what about editing?
Editing items is also fairly straightforward, as you can see in Listing 6.
Listing 6. Editing items
<h1 align="center"><xforms:output ref="/rdf:RDF/rss:channel/rss:title" /></h1>
<xforms:repeat id="itemlist" nodeset="/rdf:RDF/rss:item">
<xforms:input ref="rss:title">
<xforms:label>Title: </xforms:label>
</xforms:input>
<xforms:input ref="rss:description">
<xforms:label>Excerpt: </xforms:label>
</xforms:input>
<xforms:input ref="rss:link">
<xforms:label>URL</xforms:label>
</xforms:input>
<xforms:input ref="dc:subject">
<xforms:label>Category</xforms:label>
</xforms:input>
<xforms:input ref="dc:date">
<xforms:label>Created</xforms:label>
</xforms:input>
</xforms:repeat>
<xforms:repeat nodeset="/rdf:RDF/rss:item">
<h2><xforms:output ref="rss:title"/></h2>
...
|
Once again, you loop through the individual rss:item elements, this time using input elements rather than output elements. You'll give the repeat element an id value so you can refer to it later. The result looks like Figure 4.
Figure 4. The editor
Now, this makes it easy to edit visible data, but what about data that's not immediately obvious, such as the rdf:about attribute or the rdf:Seq list that appears at the top of the file?
RSS 1.0 includes a separate section that lists the URLs of individual items, and an attribute that also lists the URL. You need to make sure that if the URL is changed in the form, this information propogates to these nodes. To do that, you can use a combination of techniques (see Listing 7).
Listing 7. Updating values
...
<xforms:model>
<xforms:instance id="content" src="samplerssfeed.xml" />
<xforms:bind nodeset="/rdf:RDF/rss:item/@rdf:about" calculate="../rss:link" />
</xforms:model>
...
<xforms:input ref="rss:link">
<xforms:label>URL</xforms:label>
<xforms:action ev:event="xforms-value-changed">>
<xforms:setvalue
ref="/rdf:RDF/rss:channel/rss:items/rdf:Seq/rdf:li[index('itemlist')]
/@rdf:resource"
value="/rdf:RDF/rss:item[index('itemlist')]/rss:link" />
</xforms:action>
</xforms:input>
<xforms:input ref="dc:subject">
...
|
It's easy to keep the rdf:about attribute in sync; you can use use a bind element. This element makes sure that no matter what you do, the value of a particular item's rdf:about attribute always matches the value of its rss:link child.
The rdf:Seq list is another matter; because they are in different parts of the DOM, you can't use a simple bind element. For this list, you will need to listen for the event that signifies that the value of the rss:link field has been changed, and when it has, you need to manually reset the rdf:li element, using the index of the repeat element -- remember how you gave it an id value to refer to it later? -- to coordinate positions.
Now you need to save the feed.
Saving the feed is a matter of creating a submission element that sends the instance data to a script that can save it (see Listing 8).
Listing 8. Creating a
submission element
...
<xforms:model>
<xforms:instance id="content" src="samplerssfeed.xml" />
<xforms:submission id="submitrsstosave" action="http://localhost/rssxforms.php"
instance="content" replace="instance" method="post"/>
<xforms:bind nodeset="/rdf:RDF/rss:item/@rdf:about" calculate="../rss:link" />
</xforms:model>
...
<h1 align="center"><xforms:output ref="/rdf:RDF/rss:channel/rss:title" /></h1>
<xforms:submit submission="submitrsstosave">
<xforms:label>Save Feed</xforms:label>
</xforms:submit>
<xforms:repeat id="itemlist" nodeset="/rdf:RDF/rss:item">
...
|
You don't want the entire page to go anywhere when the user saves the feed, so just replace the instance. The target of this submission is a simple script that reads the data, saves it, and echoes it back out for the instance to re-populate itself (see Listing 9).
Listing 9. The script to save the data
<?php
if (!isset($HTTP_RAW_POST_DATA))
$HTTP_RAW_POST_DATA = file_get_contents("php://input");
$filename = "C:/SW/public_html/savedfeed.xml";
$fh = fopen($filename, 'w');
fwrite($fh, $HTTP_RAW_POST_DATA);
fclose($fh);
header("Content-type: text/plain");
echo $HTTP_RAW_POST_DATA;
?>
|
In this case, you're saving the file to a specific location, independent of the actual URL for the feed. In an actual application, you'll likely want to base the location of the saved file on the actual feed itself. Most feeds include their original location. For example, your sample feed lists the original location in the rdf:RDF/rss:link element.
You can also use the PUT method if your server supports it.
Now let's look at adding items to the feed.
Adding new items is a matter of adding an "insert" button with the appropriate action (see Listing 10).
Listing 10. Adding new items
...
<h1 align="center"><xforms:output ref="/rdf:RDF/rss:channel/rss:title" /></h1>
<xforms:trigger>
<xforms:label>Add New item</xforms:label>
<xforms:action ev:event="DOMActivate">
<xforms:insert nodeset="/rdf:RDF/rss:item" at="1" position="before"/>
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/@rdf:about" />
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/rss:title" />
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/rss:link" />
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/rss:description" />
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/dc:subject" />
<xforms:setvalue ref="/rdf:RDF/rss:item[1]/dc:date" value="now()" />
<xforms:insert nodeset="/rdf:RDF/rss:channel/rdf:Seq/rdf:li" at="1"
position="before"/>
<xforms:setvalue ref="/rdf:RDF/rss:channel/rdf:Seq/rdf:li/@rdf:resource" />
</xforms:action>
</xforms:trigger>
<xforms:submit submission="submitrsstosave">
<xforms:label>Save Feed</xforms:label>
</xforms:submit>
...
|
When the user clicks the Add New Item button, it clones the last rdf:RDF/rss:item element and adds the clone before the first existing one. Because it includes the old data, however, you need to use the setvalue element to clear out the existing data. The one exception to this rule is the dc:date value, which you can set using the now() function, part of XForms. (You may also want to set this value when any of the other data changes, but I'll leave that as an exercise for you, the reader.)
You also need to add the rdf:li node to the top of the file and clear its rdf:resource attibute.
Finally, you need to give the user the ability to delete individual items (see Listing 11).
Listing 11. Deleting individual items
...
<xforms:input ref="dc:date">
<xforms:label>Created</xforms:label>
</xforms:input>
<xforms:trigger>
<xforms:label>Delete</xforms:label>
<xforms:action ev:event="DOMActivate">
<xforms:delete nodeset="." at="1" />
</xforms:action>
</xforms:trigger>
</xforms:repeat>
...
|
Because you're including the trigger right in the repeat, deciding which element to delete is easy; it's always the current one.
The final step is to give the user control over which form to use. When the form first loads, you want the user to see the "reader," but you want him or her to have the ability to change over to the "editor" view and back (see Listing 12).
Listing 12. Choosing the form
...
<h1 align="center"><xforms:output ref="/rdf:RDF/rss:channel/rss:title" /></h1>
<xforms:switch>
<xforms:case id="edit" selected="false">
<xforms:trigger id="readButton">
<xforms:label>Reader</xforms:label>
<xforms:toggle ev:event="DOMActivate"
case="read"/>
</xforms:trigger>
<xforms:trigger>
<xforms:label>Add New item</xforms:label>
<xforms:action ev:event="DOMActivate">
...
<xforms:delete nodeset="." at="1" />
</xforms:action>
</xforms:trigger>
</xforms:repeat>
</xforms:case>
<xforms:case id="read" selected="true">
<xforms:trigger id="editButton">
<xforms:label>Editor</xforms:label>
<xforms:toggle ev:event="DOMActivate"
case="edit"/>
</xforms:trigger>
<xforms:repeat nodeset="/rdf:RDF/rss:item">
<h2><xforms:output ref="rss:title"/></h2>
<xforms:output ref="rss:description"/>
<xforms:output ref="rss:link"/>
<xforms:output ref="dc:date" />
<i><xforms:output ref="dc:subject" /></i>
</xforms:repeat>
</xforms:case>
</xforms:switch>
</body>
</html>
|
The switch element works like a case statement in other programming languages. Here you're starting with a case of "read" -- using selected="true" -- and adding a trigger that enables the user to toggle that value to edit. When the user clicks it, the case, and therefore the view, changes. The edit case is similar, toggling over to the reader form.
XForms provides an excellent basis for editing RSS, Atom, and other XML-based syndication formats. In this article, you created a form that enables the user to choose a feed, read it, edit it, and save it to a file. In a production application, you will also need to determine the version of the feed at hand and alter your forms accordingly.
| Description | Name | Size | Download method |
|---|---|---|---|
| XForms RSS Reader source code | rssreader_source.zip | 5KB | HTTP |
Information about download methods
Learn
-
Read the XForms specification.
-
Read the XForms 2.0 specification.
-
Learn about the different versions of RSS.
-
Learn the basics of XForms: Part 1, Part 2, and Part 3.
-
Learn more about the switch/case elements.
-
Learn more about RSS.
-
Learn about Atom.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
- Let
Skimstone explain to you what XForms is.
- See the
Skimstone introduction to XForms.
Get products and technologies
-
Download Mozilla Firefox.
-
Download the XForms extension.
-
Get MozzIE, an open source control that allows you to render XForms in IE.
Discuss
- Participate in the discussion forum.
-
Atom and RSS forum: Find tips, tricks, and answers about Atom, RSS, or other syndication topics in this forum.
Nicholas Chase has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, an Oracle instructor, and the Chief Technology Officer of an interactive communications company. He is the author of several books, including XML Primer Plus (Sams).




